package cn.wdm.ymcc.service.impl;

import cn.wdm.ymcc.domain.Course;
import cn.wdm.ymcc.domain.CourseMarket;
import cn.wdm.ymcc.domain.CourseOrder;
import cn.wdm.ymcc.domain.CourseOrderItem;
import cn.wdm.ymcc.dto.PayDto;
import cn.wdm.ymcc.exception.GloablException;
import cn.wdm.ymcc.feign.CourseFeignClient;
import cn.wdm.ymcc.mapper.CourseOrderMapper;
import cn.wdm.ymcc.result.JSONResult;
import cn.wdm.ymcc.service.ICourseOrderItemService;
import cn.wdm.ymcc.service.ICourseOrderService;
import cn.wdm.ymcc.util.AssertUtil;
import cn.wdm.ymcc.util.CodeGenerateUtils;
import cn.wdm.ymcc.util.StrUtils;
import cn.wdm.ymcc.vo.CourseItemVo;
import cn.wdm.ymcc.vo.CourseOrderVo;
import cn.wdm.ymcc.vo.SubmitOrderParamVo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.fasterxml.jackson.annotation.JsonFormat;
import javafx.util.StringConverter;
import org.apache.commons.lang.StringUtils;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.json.JSONString;
import org.json.JSONStringer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author lao.zhang
 * @since 2022-07-29
 */
@Service
public class CourseOrderServiceImpl extends ServiceImpl<CourseOrderMapper, CourseOrder> implements ICourseOrderService {

    @Autowired
    private ICourseOrderItemService courseOrderItemService;
    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;
    @Autowired
    private CourseFeignClient courseFeignClient;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    /**
     * 下单逻辑，提交订单按钮
     * @param vo
     */
    @Override
    @Transactional
    public String placeOrder(SubmitOrderParamVo vo) {
        Long loginId = 1L;
        //校验当前的人购买的课程是不是重复下单
        //0.先查询出该用户下的所有成功支付的订单。先根据userid和支付成功状态查询出主订单，状态为成功支付就不能下单。
        /*Wrapper<CourseOrder> wrapper=new EntityWrapper<CourseOrder>();
        wrapper.eq("user_id",loginId);
        wrapper.eq("status_order",1);
        List<CourseOrder> courseOrders = selectList(wrapper);
        //0.1：再查询购买的课程有没有被成功支付
        if (courseOrders!=null) {
            courseOrders.forEach(courseOrder -> {
                vo.getCourseIds().forEach(courseId -> {
                    Wrapper<CourseOrderItem> wrapper1=new EntityWrapper<CourseOrderItem>();
                    wrapper1.eq("order_no",courseOrder.getOrderNo());
                    wrapper1.eq("course_id",courseId);
                    CourseOrderItem courseOrderItem = courseOrderItemService.selectOne(wrapper1);
                    AssertUtil.isNull(courseOrderItem,"该订单已经支付");
                });
            });
        }*/

        //1.参数校验 JSR303校验
        AssertUtil.isNotNull(vo,"参数为空");
        //1.1 参数获取与转换
        List<Long> courseIds = vo.getCourseIds();
        String token = vo.getToken();
        //将List<Long>转为string
        String courseIdsStr = StringUtils.join(courseIds, ",");
        String key=courseIdsStr+":"+loginId;//1,3:1
        //2.token是否有效
        Object tokenInRedisStr = redisTemplate.opsForValue().get(key);
        AssertUtil.isNotNull(tokenInRedisStr,"token过期或者不存在");
        AssertUtil.isEquals(token,tokenInRedisStr.toString(),"token比对不成功，下单失败");

        //3.写feign接口获取详情course+market
        JSONResult jsonResult = courseFeignClient.getCourseInfos(courseIdsStr);
        AssertUtil.isTrue(jsonResult.isSuccess(),"信息错误，下单失败！");
        AssertUtil.isNotNull(jsonResult.getData(),"服务不可达，下单失败！");

        String jsonStr = JSON.toJSONString(jsonResult.getData());//CourseOrderVo的json字符串
        CourseOrderVo courseOrderVo = JSONObject.parseObject(jsonStr, CourseOrderVo.class);
        List<CourseItemVo> courseInfos = courseOrderVo.getCourseInfos();//每个课程的couse+marken
        AssertUtil.isNotNull(courseInfos,"订单课程详情不能为空");
        BigDecimal totalAmount = courseOrderVo.getTotalAmount();//课程的总金额

        //4.保存订单（主订单，子订单）
        Date date = new Date();
        //4.1 保存主订单
        CourseOrder courseOrder = new CourseOrder();
        courseOrder.setCreateTime(date);
        courseOrder.setOrderNo(CodeGenerateUtils.generateOrderSn(loginId));
        courseOrder.setTotalAmount(totalAmount);
        courseOrder.setTotalCount(1);
        courseOrder.setStatusOrder(0);
        courseOrder.setUserId(loginId);
        courseOrder.setVersion(0);
        courseOrder.setPayType(vo.getPayType());

        //insert(courseOrder);

        //4.2 保存子订单
        StringBuilder sb = new StringBuilder();
        sb.append("【购买的课程为:");
        courseInfos.forEach(courseItemVo -> {
            //获取课程
            Course course = courseItemVo.getCourse();
            //获取课程销售相关
            CourseMarket courseMarket = courseItemVo.getCourseMarket();

            CourseOrderItem courseOrderItem = new CourseOrderItem();
            //courseOrderItem.setOrderId(courseOrder.getId());
            courseOrderItem.setAmount(courseMarket.getPrice());
            courseOrderItem.setCount(1);
            courseOrderItem.setCreateTime(date);
            courseOrderItem.setCourseId(course.getId());
            courseOrderItem.setCourseName(course.getName());
            courseOrderItem.setCoursePic(course.getPic());
            courseOrderItem.setVersion(courseOrder.getVersion());
            courseOrderItem.setOrderNo(courseOrder.getOrderNo());
            sb.append(course.getName()).append(",");
            courseOrder.getItems().add(courseOrderItem);
            //courseOrderItemService.insert(courseOrderItem);
        });
        sb.append("】!!");
        courseOrder.setTitle(sb.toString());
        //updateById(courseOrder);
        //5 往mq发送事务消息 投递消息去保存支付单
        /*重要：这里保存主子订单为本地事务，在整个下单支付的流程中，要保证保存订单本地事务和往MQ推送事务的原子性。
                因此这里要发送事务消息，往MQ中投递消息

                txProducerGroup:当前发送发送该事务的事务类型的别名，可能一个项目可能发送多个事务类型，
                但是在2.1.0版本后，移除了txProducerGroup，项目中只能有一个@RocketMQTransactionListener。
                destination:定义存放在mq的位置，topic tag
                message:是交给消费者去消费的
                arg:携带到监听的额外消息

        //保存交给事务消息中的本地事务insert(courseOrder);courseOrderItem.setOrderId(courseOrder.getId());updateById(courseOrder);
         */
        //courseOrder courseOrderItem传递

        PayDto payDto = new PayDto();
        payDto.setAmount(courseOrder.getTotalAmount());
        payDto.setPayType(courseOrder.getPayType());
        payDto.setRelationId(null);
        payDto.setOrderNo(courseOrder.getOrderNo());
        payDto.setUserId(courseOrder.getUserId());
        HashMap<Object, Object> extParam = new HashMap<>();
        extParam.put("userId",loginId);
        extParam.put("courseId",courseIdsStr);
        payDto.setExtParams(JSON.toJSONString(extParam));
        payDto.setSubject(courseOrder.getTitle());
        Message<String> message = MessageBuilder.withPayload(JSON.toJSONString(payDto)).build();
//发送事务消息开始--------------------------------------------------
        TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction(
                "courseOrderToPayTransaction",//
                "courseOrder-topic:courseOrder-tag",
                message,
                courseOrder
        );
        //本地事务执行的状态
        LocalTransactionState localTransactionState = transactionSendResult.getLocalTransactionState();
        //事务发送的状态
        SendStatus sendStatus = transactionSendResult.getSendStatus();
        if(localTransactionState!=LocalTransactionState.COMMIT_MESSAGE||sendStatus!=SendStatus.SEND_OK){
            throw new GloablException("消息发送失败,下单失败");
        }
//发送事务消息结束--------------------------------------------------
//发送延迟消息队列开始-------
        try {
            Message<String> delayMessage = MessageBuilder.withPayload(JSON.toJSONString(courseOrder.getOrderNo())).build();
            SendResult sendResult = rocketMQTemplate.syncSend(
                    "pay-timeout-message-topic:pay-timeout-message-tag",
                    delayMessage,
                    3000,
                    3
            );
            //延迟消息的发送状态
            SendStatus delayMessageStatus = sendResult.getSendStatus();
            if(delayMessageStatus != SendStatus.SEND_OK){
                 /*
                   先尝试重试3次，如果3次重试都发不出去
                     1.记录一个log表
                     2.发站内信，发短信，发邮件通知运维人员，人工接入操作
                */
            }
        } catch (Exception e) {
            e.printStackTrace();
            //报错，我们需要做补偿机制
            /*
             先尝试重试3次，如果3次重试都发不出去
             1.记录一个log表
             2.发站内信，发短信，发邮件通知运维人员，人工接入操作
             */
        }
//发送延迟消息队列结束-------
        //删除redis中的内容 删除防重复token
        redisTemplate.delete(key);
        //6.返回订单号给前端
        return courseOrder.getOrderNo();
    }
    /**
     * 将保存主子订单交给本地事务做
     * @param courseOrder
     */
    @Override
    public void savaOrderAndOrderItems(CourseOrder courseOrder) {
        //1.检验订单是否存在
        CourseOrder courseOrderTem=selectOrderByOrderNo(courseOrder.getOrderNo());
        AssertUtil.isNull(courseOrderTem,"订单存在，下单失败");
        //2.保存主订单和子订单
        insert(courseOrder);
        courseOrder.getItems().forEach(courseOrderItem -> {
            courseOrderItem.setOrderId(courseOrder.getId());
            courseOrderItemService.insert(courseOrderItem);//2.1保存子订单
        });

    }

    /**
     * 暴露的方法
     * @param orderNo
     * @return
     */
    public CourseOrder selectOrderByOrderNo(String orderNo) {
        Wrapper<CourseOrder> wrapper=new EntityWrapper<CourseOrder>();
        wrapper.eq("order_no",orderNo);
        CourseOrder courseOrder = selectOne(wrapper);
        return courseOrder;
    }
    /**
     * 支付成功修改订单的状态
     * @param courseOrder
     */
    @Override
    public void updateOrderState(CourseOrder courseOrder) {
        courseOrder.setUpdateTime(new Date());
        courseOrder.setStatusOrder(1);
        updateById(courseOrder);
    }
    /**
     * 处理超时的订单
     * @param courseOrderNo
     */
    @Override
    public void cancelOrder(String courseOrderNo) {
        //处理我们平台的超时订单，应该主动去支付宝哪里查一下，看是否真的客人付款。我这里不调用
        CourseOrder courseOrder = selectOrderByOrderNo(courseOrderNo);
        AssertUtil.isNotNull(courseOrder,"订单不存在");
        //查看订单的状态，如果订单的状态不是已支付，就修改其状态
        if(courseOrder.getStatusOrder().intValue()!=1){
            //设置超时取消
            courseOrder.setStatusOrder(4);
            updateById(courseOrder);
        }
    }
}
